#pragma once

#include "aggregator_common.h"

namespace mdtx::online::aggregator
{

    struct clock_bar
    {
        long nbin, ibin;
        double open, high, low, close, vol, tnvr, vwap;
    };

    // Aggregates quotes (price, volume, turnover) to volume clock bars (OHLC VWAP)
    class volume_clock
    {
    private:
        clock_bar res;
        bool init;
        double bsize;

    public:
        volume_clock(double volume_size)
            : res(),
              init(false),
              bsize(volume_size)
        {
        }

        template <class InValIt, class OutIdxIt, class OutBinIt, class OutValIt>
        size_t operator()(InValIt price, InValIt price_last, InValIt volume, InValIt turnover,
                          OutIdxIt ibin, OutBinIt nbin,
                          OutValIt open, OutValIt high, OutValIt low, OutValIt close, OutValIt vwap)
        {
            size_t n = 0;
            double p, v, tnvr, tvol, ap;
            if (!init)
            {
                res.open = res.high = res.low = *price;
                init = true;
            }
            while (price != price_last)
            {
                p = *price++;
                v = *volume++;
                tnvr = *turnover++;
                res.close = p;
                tvol = res.vol + v;
                if (tvol < bsize)
                {
                    res.high = std::max(p, res.high);
                    res.low = std::min(p, res.low);
                    res.tnvr += tnvr;
                    res.vol = tvol;
                }
                else
                {
                    ap = tnvr / v;
                    *open++ = res.open;
                    *high++ = res.high;
                    *low++ = res.low;
                    *close++ = res.close;
                    *vwap++ = (res.tnvr + (bsize - res.vol) * ap) / bsize;
                    // reservoir
                    res.vol = std::fmod(tvol, bsize);
                    res.tnvr = res.vol * ap;
                    res.open = res.high = res.low = p;
                    res.nbin = static_cast<long>(tvol / bsize);
                    res.ibin += res.nbin;
                    // bin & index
                    *nbin++ = res.nbin;
                    *ibin++ = res.ibin;
                    ++n;
                }
            }
            return n;
        }
    };

    // Some algorithms may only need VWAP
    class volume_clock_lite
    {
    private:
        clock_bar res;
        double bsize;

    public:
        volume_clock_lite(double volume_size)
            : res(),
              bsize(volume_size)
        {
        }

        template <class InValIt, class OutBinIt, class OutValIt>
        size_t operator()(InValIt price, InValIt price_last, InValIt volume, InValIt turnover,
                          OutBinIt nbin, OutValIt vwap)
        {
            size_t n = 0;
            double p, v, tnvr, tvol, ap;
            while (price != price_last)
            {
                p = *price++;
                v = *volume++;
                tnvr = *turnover++;
                tvol = res.vol + v;
                if (tvol < bsize)
                {
                    res.tnvr += tnvr;
                    res.vol = tvol;
                }
                else
                {
                    ap = tnvr / v;
                    *vwap++ = (res.tnvr + (bsize - res.vol) * ap) / bsize;
                    // reservoir
                    res.vol = std::fmod(tvol, bsize);
                    res.tnvr = res.vol * ap;
                    res.nbin = static_cast<long>(tvol / bsize);
                    // bin & index
                    *nbin++ = res.nbin;
                    ++n;
                }
            }
            return n;
        }
    };

    // Aggregates quotes to turnover clock bars
    class turnover_clock
    {
    private:
        clock_bar res;
        bool init;
        double bsize;

    public:
        turnover_clock(double turnover_size)
            : res(),
              init(false),
              bsize(turnover_size)
        {
        }

        template <class InValIt, class OutIdxIt, class OutBinIt, class OutValIt>
        size_t operator()(InValIt price, InValIt price_last, InValIt volume, InValIt turnover,
                          OutIdxIt ibin, OutBinIt nbin,
                          OutValIt open, OutValIt high, OutValIt low, OutValIt close, OutValIt vwap)
        {
            size_t n = 0;
            double p, v, tnvr, tot_tnvr, ap;
            if (!init)
            {
                res.open = res.high = res.low = *price;
                init = true;
            }
            while (price != price_last)
            {
                p = *price++;
                v = *volume++;
                tnvr = *turnover++;
                res.close = p;
                tot_tnvr = res.tnvr + tnvr;
                if (tot_tnvr < bsize)
                {
                    res.high = std::max(p, res.high);
                    res.low = std::min(p, res.low);
                    res.vol += v;
                    res.tnvr = tot_tnvr;
                }
                else
                {
                    ap = tnvr / v;
                    *open++ = res.open;
                    *high++ = res.high;
                    *low++ = res.low;
                    *close++ = res.close;
                    *vwap++ = bsize / (res.vol + (bsize - res.tnvr) / ap);
                    // reservoir
                    res.tnvr = std::fmod(tot_tnvr, bsize);
                    res.vol = res.tnvr / ap;
                    res.open = res.high = res.low = p;
                    res.nbin = static_cast<long>(tot_tnvr / bsize);
                    res.ibin += res.nbin;
                    // bin & index
                    *nbin++ = res.nbin;
                    *ibin++ = res.ibin;
                    ++n;
                }
            }
            return n;
        }
    };

    // VWAP only
    class turnover_clock_lite
    {
    private:
        clock_bar res;
        double bsize;

    public:
        turnover_clock_lite(double turnover_size)
            : res(),
              bsize(turnover_size)
        {
        }

        template <class InValIt, class OutBinIt, class OutValIt>
        size_t operator()(InValIt price, InValIt price_last, InValIt volume, InValIt turnover,
                          OutBinIt nbin, OutValIt vwap)
        {
            size_t n = 0;
            double p, v, tnvr, tot_tnvr, ap;
            while (price != price_last)
            {
                p = *price++;
                v = *volume++;
                tnvr = *turnover++;
                tot_tnvr = res.tnvr + tnvr;
                if (tot_tnvr < bsize)
                {
                    res.vol += v;
                    res.tnvr = tot_tnvr;
                }
                else
                {
                    ap = tnvr / v;
                    *vwap++ = bsize / (res.vol + (bsize - res.tnvr) / ap);
                    // reservoir
                    res.tnvr = std::fmod(tot_tnvr, bsize);
                    res.vol = res.tnvr / ap;
                    res.nbin = static_cast<long>(tot_tnvr / bsize);
                    // bin & index
                    *nbin++ = res.nbin;
                    ++n;
                }
            }
            return n;
        }
    };

    // Aggregates by a tumbling window
    template <class TimeType, class DurationType>
    class tumbling_window
    {
    private:
        clock_bar res;
        TimeType res_time, next, epoch;
        bool init, align;
        DurationType bsize;

    public:
        tumbling_window(DurationType window_size, TimeType epoch, bool align_time)
            : res(),
              res_time(),
              next(),
              epoch(epoch),
              init(false),
              align(align_time),
              bsize(window_size)
        {
        }

        template <class InTimeIt, class InValIt, class OutIdxIt, class OutTimeIt, class OutValIt>
        size_t operator()(InTimeIt time, InTimeIt time_last, InValIt price, InValIt volume, InValIt turnover,
                          OutIdxIt ibin, OutTimeIt tbin,
                          OutValIt open, OutValIt high, OutValIt low, OutValIt close, OutValIt vol, OutValIt tnvr, OutValIt vwap)
        {
            size_t n = 0;
            double p, v, pv, tv, tpv;
            if (!init)
            {
                res.open = res.high = res.low = p;
                res_time = *time;
                if (align)
                {
                    DurationType remain = res_time - epoch;
                    while (remain >= bsize)
                    {
                        remain -= bsize;
                    }
                    res_time -= remain;
                }
                next = res_time + bsize;
                init = true;
            }
            while (time != time_last)
            {
                res_time = *time++;
                p = *price++;
                v = *volume++;
                pv = *turnover++;
                if (res_time == next)
                {
                    // merge (t, t + bsize]
                    *open++ = res.open;
                    *high++ = std::max(p, res.high);
                    *low++ = std::min(p, res.low);
                    *close++ = p;
                    tv = res.vol + v;
                    tpv = res.tnvr + pv;
                    *vol++ = tv;
                    *tnvr++ = tpv;
                    *vwap++ = tpv / tv;
                    // res
                    res.open = res.high = res.low = res.close = p;
                    res.vol = res.tnvr = 0;
                    // index & time
                    res.ibin += 1;
                    *ibin++ = res.ibin;
                    *tbin++ = next;
                    next += bsize;
                    // n
                    ++n;
                }
                else if (res_time > next)
                {
                    // do not merge
                    *open++ = res.open;
                    *high++ = res.high;
                    *low++ = res.low;
                    *close++ = res.close;
                    *vol++ = res.vol;
                    *tnvr++ = res.tnvr;
                    *vwap++ = res.tnvr / res.vol;
                    // res
                    res.open = res.high = res.low = res.close = p;
                    res.vol = v;
                    res.tnvr = pv;
                    // index & time
                    res.ibin += 1;
                    *ibin++ = res.ibin;
                    *tbin++ = next;
                    next += bsize;
                    while (next < res_time)
                    {
                        next += bsize;
                        res.ibin += 1;
                    }
                    // n
                    ++n;
                }
                else
                {
                    res.high = std::max(p, res.high);
                    res.low = std::min(p, res.low);
                    res.close = p;
                    res.vol += v;
                    res.tnvr += pv;
                }
            }
            return n;
        }
    };

    // Vol & VWAP only
    template <class TimeType, class DurationType>
    class tumbling_window_lite
    {
    private:
        clock_bar res;
        TimeType res_time, next, epoch;
        bool init, align;
        DurationType bsize;

    public:
        tumbling_window_lite(DurationType window_size, TimeType epoch, bool align_time)
            : res(),
              res_time(),
              next(),
              epoch(epoch),
              init(false),
              align(align_time),
              bsize(window_size)
        {
        }

        template <class InTimeIt, class InValIt, class OutTimeIt, class OutValIt>
        size_t operator()(InTimeIt time, InTimeIt time_last, InValIt price, InValIt volume, InValIt turnover,
                          OutTimeIt tbin, OutValIt vwap, OutValIt vol)
        {
            size_t n = 0;
            double p, v, pv, tv, tpv;
            if (!init)
            {
                res_time = *time;
                if (align)
                {
                    DurationType remain = res_time - epoch;
                    while (remain >= bsize)
                    {
                        remain -= bsize;
                    }
                    res_time -= remain;
                }
                next = res_time + bsize;
                init = true;
            }
            while (time != time_last)
            {
                res_time = *time++;
                p = *price++;
                v = *volume++;
                pv = *turnover++;
                if (res_time == next)
                {
                    // merge (t, t + bsize]
                    tv = res.vol + v;
                    tpv = res.tnvr + pv;
                    *vol++ = tv;
                    *vwap++ = tpv / tv;
                    // res
                    res.vol = res.tnvr = 0;
                    // index & time
                    *tbin++ = next;
                    next += bsize;
                    // n
                    ++n;
                }
                else if (res_time > next)
                {
                    // do not merge
                    *vol++ = res.vol;
                    *vwap++ = res.tnvr / res.vol;
                    // res
                    res.vol = v;
                    res.tnvr = pv;
                    // index & time
                    *tbin++ = next;
                    next += bsize;
                    while (next < res_time)
                    {
                        next += bsize;
                    }
                    // n
                    ++n;
                }
                else
                {
                    res.vol += v;
                    res.tnvr += pv;
                }
            }
            return n;
        }
    };

    // Aggregates by a sliding window. Notice: O(log M) datum due to rolling min/max, M is window size
    template <class TimeType, class DurationType>
    class sliding_window
    {
    private:
        clock_bar res;
        boost::circular_buffer<double> pbuf, vbuf, pvbuf, hbuf, lbuf;
        boost::circular_buffer<TimeType> tbuf;
        DurationType bsize;

    public:
        sliding_window(DurationType window_size, size_t max_buf_size)
            : res(),
              pbuf(max_buf_size),
              vbuf(max_buf_size),
              pvbuf(max_buf_size),
              hbuf(max_buf_size),
              lbuf(max_buf_size),
              tbuf(max_buf_size),
              bsize(window_size)
        {
        }

        template <class InTimeIt, class InValIt, class OutIdxIt, class OutTimeIt, class OutValIt>
        size_t operator()(InTimeIt time, InTimeIt time_last, InValIt price, InValIt volume, InValIt turnover,
                          OutIdxIt ibin, OutTimeIt tbin,
                          OutValIt open, OutValIt high, OutValIt low, OutValIt close, OutValIt vol, OutValIt tnvr, OutValIt vwap)
        {
            size_t n = 0;
            TimeType t;
            double p, v, pv, tmp;
            while (time != time_last)
            {
                t = *time++;
                p = *price++;
                v = *volume++;
                pv = *turnover++;
                // bar
                res.ibin += 1;
                res.vol += v;
                res.tnvr += pv;
                // insert
                pbuf.push_front(p);
                vbuf.push_front(v);
                pvbuf.push_front(pv);
                // high, low: O(log N)
                while (!hbuf.empty() && hbuf.back() < p)
                    hbuf.pop_back();
                while (!lbuf.empty() && lbuf.back() > p)
                    lbuf.pop_back();
                hbuf.push_back(p);
                lbuf.push_back(p);
                // time
                tbuf.push_front(t);
                // remove
                while ((t - tbuf.back()) > bsize)
                {

                    // high, low
                    if (hbuf.front() == pbuf.back())
                        hbuf.pop_front();
                    if (lbuf.front() == pbuf.back())
                        lbuf.pop_front();
                    // vol, tnvr
                    res.vol -= vbuf.back();
                    res.tnvr -= pvbuf.back();
                    pbuf.pop_back();
                    vbuf.pop_back();
                    pvbuf.pop_back();
                    // time
                    tbuf.pop_back();
                }

                *open++ = pbuf.back();
                *high++ = hbuf.front();
                *low++ = lbuf.front();
                *close++ = p; //pbuf.front();
                *vol = res.vol;
                *tnvr++ = res.tnvr;
                *vwap++ = res.tnvr / res.vol;

                *ibin++ = res.ibin;
                *tbin++ = t;

                ++n;
            }
            return n;
        }
    };

    // Vol & VWAP only
    template <class TimeType, class DurationType>
    class sliding_window_lite
    {
    private:
        clock_bar res;
        boost::circular_buffer<double> pbuf, vbuf, pvbuf;
        boost::circular_buffer<TimeType> tbuf;
        DurationType bsize;

    public:
        sliding_window_lite(DurationType window_size, size_t max_buf_size)
            : res(),
              pbuf(max_buf_size),
              vbuf(max_buf_size),
              pvbuf(max_buf_size),
              tbuf(max_buf_size),
              bsize(window_size)
        {
        }

        template <class InTimeIt, class InValIt, class OutTimeIt, class OutValIt>
        size_t operator()(InTimeIt time, InTimeIt time_last, InValIt price, InValIt volume, InValIt turnover,
                          OutTimeIt tbin, OutValIt vwap, OutValIt vol)
        {
            size_t n = 0;
            TimeType t;
            double p, v, pv;
            while (time != time_last)
            {
                t = *time++;
                p = *price++;
                v = *volume++;
                pv = *turnover++;

                pbuf.push_front(p);
                vbuf.push_front(v);
                pvbuf.push_front(pv);
                tbuf.push_front(t);

                res.vol += v;
                res.tnvr += pv;
                while ((t - tbuf.back()) > bsize)
                {
                    res.vol -= vbuf.back();
                    res.tnvr -= pvbuf.back();
                    pbuf.pop_back();
                    vbuf.pop_back();
                    pvbuf.pop_back();
                    tbuf.pop_back();
                }

                *vol = res.vol;
                *vwap++ = res.tnvr / res.vol;
                *tbin++ = t;

                ++n;
            }
            return n;
        }
    };

}
